package gagak.framework.web.paramresovler;


import gagak.framework.web.annonation.Nullable;
import gagak.framework.web.param.HandlerMethod;
import gagak.framework.web.param.MethodParameter;
import gagak.framework.web.reflect.ClassUtils;
import gagak.framework.web.reflect.ReflectionUtils;
import gagak.framework.web.support.HttpStatus;
import gagak.framework.web.support.request.NativeWebRequest;
import gagak.framework.web.support.request.ServletWebRequest;
import gagak.framework.web.utils.StringUtils;
import gagak.framework.web.view.View;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;

/*
 * 使用组合模式，组合使用参数解析
 */

public class InvocableHandlerMethod extends HandlerMethod {

    // 方法参数解析
    private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
    //方法返回处理器
    private HandlerMethodReturnValueHandlerComposite returnValueHandlers = new HandlerMethodReturnValueHandlerComposite();

    private static final Logger logger = LoggerFactory.getLogger(InvocableHandlerMethod.class);

    /*
     * Create an instance from a {@code HandlerMethod}.
     */
    public InvocableHandlerMethod(HandlerMethod handlerMethod) {
        super(handlerMethod);
    }


    public InvocableHandlerMethod(Object bean, Method method) {
        super(bean, method);
    }


    public InvocableHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes)
            throws NoSuchMethodException {

        super(bean, methodName, parameterTypes);
    }


    /*
     * Set {@link HandlerMethodArgumentResolver}s to use to use for resolving method argument values.
     */
    public void setHandlerMethodArgumentResolvers(HandlerMethodArgumentResolverComposite argumentResolvers) {
        this.resolvers = argumentResolvers;
    }

    public Object invokeAndHandle(ServletWebRequest webRequest, Object... providedArgs) throws Exception {

        // 对目标handler的参数进行处理，并且调用目标handler
        Object returnValue = invokeForRequest(webRequest, providedArgs);
        // 设置相关的返回状态
        setResponseStatus(webRequest);

        // 如果请求处理完成，则设置requestHandled属性
        if (returnValue == null) {
            if (isRequestNotModified(webRequest) || getResponseStatus() != null
            ) {
                return null;
            }
        } else if (StringUtils.hasText(getResponseStatusReason())) {
            // 如果请求失败，但是有错误原因，那么也会设置requestHandled属性
            return null;
        }

        //Assert.state(this.returnValueHandlers != null, "No return value handlers");
      /*  try {
            // 遍历当前容mvc器中所有ReturnValueHandler，判断哪种handler支持当前返回值的处理，
            // 如果支持，则使用该handler处理该返回值 // TODO: 2022/12/1  这里不需要进行结果解析
          *//*  this.returnValueHandlers.handleReturnValue(
                    returnValue, getReturnValueType(returnValue), webRequest);*//*
        } catch (Exception ex) {
            if (logger.isTraceEnabled()) {
             *//*   logger.trace(getReturnValueHandlingErrorMessage("Error handling return value",
                        returnValue), ex);*//*
            }
            throw ex;
        }*/
        return returnValue;
    }

    public HandlerMethodArgumentResolverComposite getResolvers() {
        return resolvers;
    }

    public void setResolvers(HandlerMethodArgumentResolverComposite resolvers) {
        this.resolvers = resolvers;
    }

    public HandlerMethodReturnValueHandlerComposite getReturnValueHandlers() {
        return returnValueHandlers;
    }

    public void setReturnValueHandlers(HandlerMethodReturnValueHandlerComposite returnValueHandlers) {
        this.returnValueHandlers = returnValueHandlers;
    }

    private boolean isRequestNotModified(ServletWebRequest webRequest) {
        return webRequest.isNotModified();
    }

    private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
        HttpStatus status = getResponseStatus();
        if (status == null) {
            return;
        }

        HttpServletResponse response = webRequest.getResponse();
        if (response != null) {
            String reason = getResponseStatusReason();
            if (StringUtils.hasText(reason)) {
                response.sendError(status.value(), reason);
            } else {
                response.setStatus(status.value());
            }
        }

        // To be picked up by RedirectView
        webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, status);
    }

    protected HttpStatus getResponseStatus() {
        return this.responseStatus;
    }


    /*
     * 对于handler的调用过程，这里主要分为三个步骤：
     * ①处理请求参数进行处理，将request中的参数封装为当前handler的参数的形式；
     * ②通过反射调用当前handler；
     * ③对方法的返回值进行处理，以将其封装为一个ModleAndView对象。这里第一步和第二步封装在了invokeForRequest()方法中;：
     */
    @Nullable
    public Object invokeForRequest(NativeWebRequest request,
                                   Object... providedArgs) throws Exception {

        // 将request中的参数转换为当前handler的参数形式
        Object[] args = getMethodArgumentValues(request, providedArgs);
        if (logger.isTraceEnabled()) {
            logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(),
                    getControllerType()) + "' with arguments " + Arrays.toString(args));
        }
        // 这里doInvoke()方法主要是结合处理后的参数，使用反射对目标方法进行调用
        Object returnValue = doInvoke(args);
        if (logger.isTraceEnabled()) {
            logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(),
                    getControllerType()) + "] returned [" + returnValue + "]");
        }
        return returnValue;

    }


    /*
     * 本方法主要是通过当前容器中配置的ArgumentResolver对request中的参数进行转化，
     * 将其处理为目标handler的参数的形式
     */
    private Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable
            Object... providedArgs) throws Exception {

        // 获取当前handler所声明的所有参数，主要包括参数名，参数类型，参数位置，所标注的注解等等属性
        MethodParameter[] parameters = getMethodParameters();
        Object[] args = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            MethodParameter parameter = parameters[i];
            // providedArgs是调用方提供的参数，这里主要是判断这些参数中是否有当前类型
            // 或其子类型的参数，如果有，则直接使用调用方提供的参数，对于请求处理而言，默认情况下，
            // 调用方提供的参数都是长度为0的数组
            args[i] = resolveProvidedArgument(parameter, providedArgs);
            if (args[i] != null) {
                continue;
            }

            // 如果在调用方提供的参数中不能找到当前类型的参数值，则遍历容器中所有的
            // ArgumentResolver，判断哪种类型的Resolver支持对当前参数的解析，这里的判断
            // 方式比较简单，比如RequestParamMethodArgumentResolver就是判断当前参数
            // 是否使用@RequestParam注解进行了标注
            if (this.resolvers.supportsParameter(parameter)) {
                try {
                    // 如果能够找到对当前参数进行处理的ArgumentResolver，则调用其
                    // resolveArgument()方法从request中获取对应的参数值，并且进行转换
                    args[i] = this.resolvers.resolveArgument(
                            parameter, request);
                    continue;
                } catch (Exception ex) {
                    if (logger.isDebugEnabled()) {
                        logger.debug(getArgumentResolutionErrorMessage("Failed to resolve",
                                i), ex);
                    }
                    throw ex;
                }
            }

            // 如果进行了参数处理之后当前参数还是为空，则抛出异常
            if (args[i] == null) {
                throw new IllegalStateException("Could not resolve method parameter at index "
                        + parameter.getParameterIndex() + " in "
                        + ": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));
            }
        }
        return args;
    }

    private String getArgumentResolutionErrorMessage(String text, int index) {
        Class<?> paramType = getMethodParameters()[index].getParameterType();
        return text + " argument " + index + " of type '" + paramType.getName() + "'";
    }

    /*
     * Attempt to resolve a method parameter from the list of provided argument values.
     */
    @Nullable
    private Object resolveProvidedArgument(MethodParameter parameter, @Nullable Object... providedArgs) {
        if (providedArgs == null) {
            return null;
        }
        for (Object providedArg : providedArgs) {
            if (parameter.getParameterType().isInstance(providedArg)) {
                return providedArg;
            }
        }
        return null;
    }


    /*
     * Invoke the handler method with the given argument values.
     */
    protected Object doInvoke(Object... args) throws Exception {
        ReflectionUtils.makeAccessible(getMethod());
        try {
            return getMethod().invoke(getController(), args);
        } catch (IllegalArgumentException ex) {
            String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
            throw new IllegalStateException(getInvocationErrorMessage(text, args), ex);
        } catch (InvocationTargetException ex) {
            // Unwrap for HandlerExceptionResolvers ...
            Throwable targetException = ex.getTargetException();
            if (targetException instanceof RuntimeException) {
                throw (RuntimeException) targetException;
            } else if (targetException instanceof Error) {
                throw (Error) targetException;
            } else if (targetException instanceof Exception) {
                throw (Exception) targetException;
            } else {
                String text = getInvocationErrorMessage("Failed to invoke handler method", args);
                throw new IllegalStateException(text, targetException);
            }
        }
    }


    private void assertTargetBean(Method method, Object targetBean, Object[] args) {
        Class<?> methodDeclaringClass = method.getDeclaringClass();
        Class<?> targetBeanClass = targetBean.getClass();
        if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) {
            String text = "The mapped handler method class '" + methodDeclaringClass.getName() +
                    "' is not an instance of the actual controller bean class '" +
                    targetBeanClass.getName() + "'. If the controller requires proxying " +
                    "(e.g. due to @Transactional), please use class-based proxying.";
            throw new IllegalStateException(getInvocationErrorMessage(text, args));
        }
    }

    private String getInvocationErrorMessage(String text, Object[] resolvedArgs) {
        StringBuilder sb = new StringBuilder(getDetailedErrorMessage(text));
        sb.append("Resolved arguments: \n");
        for (int i = 0; i < resolvedArgs.length; i++) {
            sb.append("[").append(i).append("] ");
            if (resolvedArgs[i] == null) {
                sb.append("[null] \n");
            } else {
                sb.append("[type=").append(resolvedArgs[i].getClass().getName()).append("] ");
                sb.append("[value=").append(resolvedArgs[i]).append("]\n");
            }
        }
        return sb.toString();
    }

    /*
     *
     * @param text error message to append the HandlerMethod details to
     */
    protected String getDetailedErrorMessage(String text) {
        StringBuilder sb = new StringBuilder(text).append("\n");
        sb.append("HandlerMethod details: \n");
        sb.append("Controller [").append(getControllerType().getName()).append("]\n");
        sb.append("Method [").append(getMethod().toGenericString()).append("]\n");
        return sb.toString();
    }

}
